<?php

namespace Desktopd\PlatformTools;


class Random {
    public static $isSecure = true;
    
    // this is not only secure, but more accurate (in 64-bit) and significantly slower
    private static function openssl($a, $b) {
        list($a, $b) = array_map(function($n){
            return (int) $n;
        }, array($a, $b));
        
        $min = min($a, $b);
        $max = max($a, $b);
        
        $offset = $min;
        $r = $max - $min;
        
        if ($a == $b) {
            return $a;
        }
        
        $n = log($r, 2);
        
        $bytesLength = ceil($n / 8);
        
        do {
            $rand = 0;
            
            if (!function_exists('openssl_random_pseudo_bytes')) {
                throw new \RuntimeException("openssl_random_pseudo_bytes() is not supported");
            }
            
            $bytes = openssl_random_pseudo_bytes($bytesLength, $cstrong);
            if (!$cstrong) {
                throw new \RuntimeException("Not secure random generator!");
            }
            
            if (strlen($bytes) != $bytesLength) {
                throw new \RuntimeException("Random bytes of wrong length");
            }
            
            for ($i = 0; $i < $bytesLength; $i++) {
                $byte = ord($bytes[$i]);
                for ($j = 0; $j < 8; $j++) {
                    if (($byte & pow(2, $j)) > 0) {
                        $rand |= pow(2, $i * 8 + $j);
                    }
                }
            }
            
        } while ($rand > $r || $rand < 0);
        
        $rand += $offset;
        return $rand;
    }
    
    public static function rand($min, $max) {
        try {
            if (!self::$isSecure) throw new \Exception();
            return self::openssl($min, $max);
        } catch (\Exception $e) {
            return mt_rand($min, $max);
        }
    }
    
    public static function randMax() {
        if (self::$isSecure && function_exists('openssl_random_pseudo_bytes')) {
            return PHP_INT_MAX;
        }
        return mt_getrandmax();
    }
    
    public static function float($min = 0, $max = 1) {
        return $min + self::rand(0, self::randMax()) / self::randMax() * ($max - $min);
    }
    
    public static function between01($ifMin = true, $ifMax = true) {
        do {
            $rand = self::float();
        } while (((!$ifMin) && ($rand <= 0)) || ((!$ifMax) && ($rand >= 1)));
        
        return $rand;
    }
    
    public static function normal($avg = 0, $stdDev = 1) {
        $a = self::between01(false, false);
        $b = self::between01(false, false);
        return sqrt(-2 * log($a)) * sin(2 * pi() * $b) * $stdDev + $avg;
    }
    
    public static function exponential($rate = 1) {
        return -log(self::between01(false, true)) / $rate;
        // -ln( [Uniform in (0,1)] ) / [rate]
    }
    
    public static function exponentialByMean ($avg = 1) {
        return -log(self::between01(false, true)) * $avg;
    }
    
    public static function exponentialByMedian ($median = 1) {
        return -log(self::between01(false, true)) * $avg * log(2);
    }
    
    public static function exponentialByBase ($base = 0.5) {
        if ($base >= 1 || $base <= 0) return false;
        return self::exponential(-log($base));
    }
    
    public static function exponentialByBaseInverse ($inverse = 2) {
        if ($inverse <= 1) return false;
        return self::exponential(log($inverse));
    }
    
    public static function xenakis() {
        return 1 - sqrt(self::between01());
    }
    
    public static function prob($prob = 0) {
        return self::float(0, 1) <= max(0, min(1, $prob));
    }
}


// vim: ts=4 et ai

